/**
 * @author mrdoob / http://mrdoob.com/
 */

import * as THREE from './build/three.module.js'

import {ThreeMFLoader} from './jsm/loaders/3MFLoader.js'
import {AMFLoader} from './jsm/loaders/AMFLoader.js'
import {ColladaLoader} from './jsm/loaders/ColladaLoader.js'
import {DRACOLoader} from './jsm/loaders/DRACOLoader.js'
import {FBXLoader} from './jsm/loaders/FBXLoader.js'
import {GLTFLoader} from './jsm/loaders/GLTFLoader.js'
import {KMZLoader} from './jsm/loaders/KMZLoader.js'
import {MD2Loader} from './jsm/loaders/MD2Loader.js'
import {MTLLoader} from './jsm/loaders/MTLLoader.js'
import {OBJLoader} from './jsm/loaders/OBJLoader.js'
import {PLYLoader} from './jsm/loaders/PLYLoader.js'
import {STLLoader} from './jsm/loaders/STLLoader.js'
import {SVGLoader} from './jsm/loaders/SVGLoader.js'
import {TDSLoader} from './jsm/loaders/TDSLoader.js'
import {VTKLoader} from './jsm/loaders/VTKLoader.js'
import {VRMLLoader} from './jsm/loaders/VRMLLoader.js'

import {AddObjectCommand} from './commands/AddObjectCommand.js'
import {SetSceneCommand} from './commands/SetSceneCommand.js'

import {LoaderUtils} from './LoaderUtils.js'

import {JSZip} from './jsm/libs/jszip.module.min.js'

var Loader = function (editor) {
  var scope = this

  this.texturePath = ''

  this.loadItemList = function (items) {
    LoaderUtils.getFilesFromItemList(items, function (files, filesMap) {
      scope.loadFiles(files, filesMap)
    })
  }

  this.loadFiles = function (files, filesMap) {
    console.log(files, filesMap)
    console.log(files.length)
    if (files.length > 0) {
      var filesMap = filesMap || LoaderUtils.createFilesMap(files)

      console.log(filesMap)

      var manager = new THREE.LoadingManager()
      manager.setURLModifier(function (url) {
        var file = filesMap[url]
        if (file) {
          console.log('Loading', url)
          return URL.createObjectURL(file)
        }
        return url
      })

      for (var i = 0; i < files.length; i++) {
        scope.loadFile(files[i], manager)
      }
    }
  }

  this.loadFile = function (file, manager) {
    var filename = file.name
    var extension = filename.split('.').pop().toLowerCase()

    var reader = new FileReader()
    reader.addEventListener('progress', function (event) {
      var size = '(' + Math.floor(event.total / 1000).format() + ' KB)'
      var progress = Math.floor((event.loaded / event.total) * 100) + '%'

      console.log('Loading', filename, size, progress)
    })

    switch (extension) {
      case '3ds':
        reader.addEventListener(
          'load',
          function (event) {
            var loader = new TDSLoader()
            var object = loader.parse(event.target.result)

            editor.execute(new AddObjectCommand(editor, object))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case '3mf':
        reader.addEventListener(
          'load',
          function (event) {
            var loader = new ThreeMFLoader()
            var object = loader.parse(event.target.result)

            editor.execute(new AddObjectCommand(editor, object))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'amf':
        reader.addEventListener(
          'load',
          function (event) {
            var loader = new AMFLoader()
            var amfobject = loader.parse(event.target.result)

            editor.execute(new AddObjectCommand(editor, amfobject))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'dae':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var loader = new ColladaLoader(manager)
            var collada = loader.parse(contents)

            collada.scene.name = filename

            editor.addAnimation(collada.scene, collada.animations)
            editor.execute(new AddObjectCommand(editor, collada.scene))
          },
          false
        )
        reader.readAsText(file)

        break

      case 'drc':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var loader = new DRACOLoader()
            loader.setDecoderPath('../examples/js/libs/draco/')
            loader.decodeDracoFile(contents, function (geometry) {
              var material = new THREE.MeshStandardMaterial()

              var mesh = new THREE.Mesh(geometry, material)
              mesh.name = filename

              editor.execute(new AddObjectCommand(editor, mesh))
            })
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'fbx':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var loader = new FBXLoader(manager)
            var object = loader.parse(contents)

            editor.addAnimation(object, object.animations)
            editor.execute(new AddObjectCommand(editor, object))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'glb':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var dracoLoader = new DRACOLoader()
            dracoLoader.setDecoderPath('../examples/js/libs/draco/gltf/')

            var loader = new GLTFLoader()
            loader.setDRACOLoader(dracoLoader)
            loader.parse(contents, '', function (result) {
              var scene = result.scene
              scene.name = filename

              editor.addAnimation(scene, result.animations)
              editor.execute(new AddObjectCommand(editor, scene))
            })
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'gltf':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var loader

            if (isGLTF1(contents)) {
              alert('Import of glTF asset not possible. Only versions >= 2.0 are supported. Please try to upgrade the file to glTF 2.0 using glTF-Pipeline.')
            } else {
              var dracoLoader = new DRACOLoader()
              dracoLoader.setDecoderPath('../examples/js/libs/draco/gltf/')

              loader = new GLTFLoader(manager)
              loader.setDRACOLoader(dracoLoader)
            }

            loader.parse(contents, '', function (result) {
              var scene = result.scene
              scene.name = filename

              editor.addAnimation(scene, result.animations)
              editor.execute(new AddObjectCommand(editor, scene))
            })
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'js':
      case 'json':

      case '3geo':
      case '3mat':
      case '3obj':
      case '3scn':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            // 2.0

            if (contents.indexOf('postMessage') !== -1) {
              var blob = new Blob([contents], {
                type: 'text/javascript',
              })
              var url = URL.createObjectURL(blob)

              var worker = new Worker(url)

              worker.onmessage = function (event) {
                event.data.metadata = {
                  version: 2,
                }
                handleJSON(event.data)
              }

              worker.postMessage(Date.now())

              return
            }

            // >= 3.0

            var data

            try {
              data = JSON.parse(contents)
            } catch (error) {
              alert(error)
              return
            }

            handleJSON(data)
          },
          false
        )
        reader.readAsText(file)

        break

      case 'kmz':
        reader.addEventListener(
          'load',
          function (event) {
            var loader = new KMZLoader()
            var collada = loader.parse(event.target.result)

            collada.scene.name = filename

            editor.execute(new AddObjectCommand(editor, collada.scene))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'md2':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var geometry = new MD2Loader().parse(contents)
            var material = new THREE.MeshStandardMaterial({
              morphTargets: true,
              morphNormals: true,
            })

            var mesh = new THREE.Mesh(geometry, material)
            mesh.mixer = new THREE.AnimationMixer(mesh)
            mesh.name = filename

            editor.addAnimation(mesh, geometry.animations)
            editor.execute(new AddObjectCommand(editor, mesh))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'obj':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var object = new OBJLoader().parse(contents)
            object.name = filename

            editor.execute(new AddObjectCommand(editor, object))
          },
          false
        )
        reader.readAsText(file)

        break

      case 'ply':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var geometry = new PLYLoader().parse(contents)
            geometry.sourceType = 'ply'
            geometry.sourceFile = file.name

            var material = new THREE.MeshStandardMaterial()

            var mesh = new THREE.Mesh(geometry, material)
            mesh.name = filename

            editor.execute(new AddObjectCommand(editor, mesh))
          },
          false
        )
        reader.readAsArrayBuffer(file)

        break

      case 'stl':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var geometry = new STLLoader().parse(contents)
            geometry.sourceType = 'stl'
            geometry.sourceFile = file.name

            var material = new THREE.MeshStandardMaterial()

            var mesh = new THREE.Mesh(geometry, material)
            mesh.name = filename

            editor.execute(new AddObjectCommand(editor, mesh))
          },
          false
        )

        if (reader.readAsBinaryString !== undefined) {
          reader.readAsBinaryString(file)
        } else {
          reader.readAsArrayBuffer(file)
        }

        break

      case 'svg':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var loader = new SVGLoader()
            var paths = loader.parse(contents).paths

            //

            var group = new THREE.Group()
            group.scale.multiplyScalar(0.1)
            group.scale.y *= -1

            for (var i = 0; i < paths.length; i++) {
              var path = paths[i]

              var material = new THREE.MeshBasicMaterial({
                color: path.color,
                depthWrite: false,
              })

              var shapes = path.toShapes(true)

              for (var j = 0; j < shapes.length; j++) {
                var shape = shapes[j]

                var geometry = new THREE.ShapeBufferGeometry(shape)
                var mesh = new THREE.Mesh(geometry, material)

                group.add(mesh)
              }
            }

            editor.execute(new AddObjectCommand(editor, group))
          },
          false
        )
        reader.readAsText(file)

        break

      case 'vtk':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var geometry = new VTKLoader().parse(contents)
            geometry.sourceType = 'vtk'
            geometry.sourceFile = file.name

            var material = new THREE.MeshStandardMaterial()

            var mesh = new THREE.Mesh(geometry, material)
            mesh.name = filename

            editor.execute(new AddObjectCommand(editor, mesh))
          },
          false
        )
        reader.readAsText(file)

        break

      case 'wrl':
        reader.addEventListener(
          'load',
          function (event) {
            var contents = event.target.result

            var result = new VRMLLoader().parse(contents)

            editor.execute(new SetSceneCommand(editor, result))
          },
          false
        )
        reader.readAsText(file)

        break

      case 'zip':
        reader.addEventListener(
          'load',
          function (event) {
            handleZIP(event.target.result)
          },
          false
        )
        reader.readAsBinaryString(file)

        break

      default:
        // alert( 'Unsupported file format (' + extension +  ').' );

        break
    }
  }

  function handleJSON(data) {
    if (data.metadata === undefined) {
      // 2.0

      data.metadata = {
        type: 'Geometry',
      }
    }

    if (data.metadata.type === undefined) {
      // 3.0

      data.metadata.type = 'Geometry'
    }

    if (data.metadata.formatVersion !== undefined) {
      data.metadata.version = data.metadata.formatVersion
    }

    switch (data.metadata.type.toLowerCase()) {
      case 'buffergeometry':
        var loader = new THREE.BufferGeometryLoader()
        var result = loader.parse(data)

        var mesh = new THREE.Mesh(result)

        editor.execute(new AddObjectCommand(editor, mesh))

        break

      case 'geometry':
        console.error('Loader: "Geometry" is no longer supported.')

        break

      case 'object':
        var loader = new THREE.ObjectLoader()
        loader.setResourcePath(scope.texturePath)

        loader.parse(data, function (result) {
          if (result.isScene) {
            editor.execute(new SetSceneCommand(editor, result))
          } else {
            editor.execute(new AddObjectCommand(editor, result))
          }
        })

        break

      case 'app':
        editor.fromJSON(data)

        break
    }
  }

  function handleZIP(contents) {
    var zip = new JSZip(contents)

    // Poly

    if (zip.files['model.obj'] && zip.files['materials.mtl']) {
      var materials = new MTLLoader().parse(zip.file('materials.mtl').asText())
      var object = new OBJLoader().setMaterials(materials).parse(zip.file('model.obj').asText())
      editor.execute(new AddObjectCommand(editor, object))
    }

    //

    zip.filter(function (path, file) {
      var manager = new THREE.LoadingManager()
      manager.setURLModifier(function (url) {
        var file = zip.files[url]

        if (file) {
          console.log('Loading', url)

          var blob = new Blob([file.asArrayBuffer()], {
            type: 'application/octet-stream',
          })
          return URL.createObjectURL(blob)
        }

        return url
      })

      var extension = file.name.split('.').pop().toLowerCase()

      switch (extension) {
        case 'fbx':
          var loader = new FBXLoader(manager)
          var object = loader.parse(file.asArrayBuffer())

          editor.execute(new AddObjectCommand(editor, object))

          break

        case 'glb':
          var loader = new GLTFLoader()
          loader.parse(file.asArrayBuffer(), '', function (result) {
            var scene = result.scene

            editor.addAnimation(scene, result.animations)
            editor.execute(new AddObjectCommand(editor, scene))
          })

          break

        case 'gltf':
          var loader = new GLTFLoader(manager)
          loader.parse(file.asText(), '', function (result) {
            var scene = result.scene

            editor.addAnimation(scene, result.animations)
            editor.execute(new AddObjectCommand(editor, scene))
          })

          break
      }
    })
  }

  function isGLTF1(contents) {
    var resultContent

    if (typeof contents === 'string') {
      // contents is a JSON string
      resultContent = contents
    } else {
      var magic = THREE.LoaderUtils.decodeText(new Uint8Array(contents, 0, 4))

      if (magic === 'glTF') {
        // contents is a .glb file; extract the version
        var version = new DataView(contents).getUint32(4, true)

        return version < 2
      } else {
        // contents is a .gltf file
        resultContent = THREE.LoaderUtils.decodeText(new Uint8Array(contents))
      }
    }

    var json = JSON.parse(resultContent)

    return json.asset != undefined && json.asset.version[0] < 2
  }
}

export {Loader}
